﻿using System;
using System.Collections;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Drawing.Design;
using System.Windows.Forms;
using System.Windows.Forms.Design;

namespace UtfprLibrary.Windows.Forms.ComponentModel {
    /// <summary>
    /// TODO
    /// </summary>
    internal class EditorServiceContextEx : IWindowsFormsEditorService, ITypeDescriptorContext {
        private readonly ComponentDesigner designer;
        private IComponentChangeService componentChangeSvc;
        private readonly PropertyDescriptor targetProperty;

        /// <summary>
        /// TODO
        /// </summary>
        /// <param name="designer"></param>
        [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
        internal EditorServiceContextEx(ComponentDesigner designer) {
            this.designer = designer;
        }

        /// <summary>
        /// TODO
        /// </summary>
        /// <param name="designer"></param>
        /// <param name="prop"></param>
        internal EditorServiceContextEx(ComponentDesigner designer, PropertyDescriptor prop) {
            this.designer = designer;
            targetProperty = prop;

            if (prop == null) {
                prop = TypeDescriptor.GetDefaultProperty(designer.Component);
                if (prop != null && typeof(ICollection).IsAssignableFrom(prop.PropertyType)) {
                    targetProperty = prop;
                }
            }

            Debug.Assert(targetProperty != null, "Need PropertyDescriptor for ICollection property to associate collectoin edtior with.");
        }

        /// <summary>
        /// TODO
        /// </summary>
        /// <param name="designer"></param>
        /// <param name="prop"></param>
        /// <param name="newVerbText"></param>
        internal EditorServiceContextEx(ComponentDesigner designer, PropertyDescriptor prop, string newVerbText)
            : this(designer, prop) {
            Debug.Assert(!string.IsNullOrEmpty(newVerbText), "newVerbText cannot be null or empty");
            this.designer.Verbs.Add(new DesignerVerb(newVerbText, OnEditItems));
        }

        /// <summary>
        /// TODO
        /// </summary>
        /// <param name="designer"></param>
        /// <param name="objectToChange"></param>
        /// <param name="propName"></param>
        /// <returns></returns>
        public static object EditValue(ComponentDesigner designer, object objectToChange, string propName) {
            // Get PropertyDescriptor
            var descriptor = TypeDescriptor.GetProperties(objectToChange)[propName];

            // Create a Context
            var context = new EditorServiceContextEx(designer, descriptor);

            // Get Editor
            var editor = descriptor.GetEditor(typeof(UITypeEditor)) as UITypeEditor;

            // Get value to edit
            var value = descriptor.GetValue(objectToChange);

            // Edit value
            var newValue = editor.EditValue(context, context, value);

            if (newValue != value) {
                try {
                    descriptor.SetValue(objectToChange, newValue);
                } catch (CheckoutException) { }
            }

            return newValue;
        }

        /// <devdoc> 
        /// Our caching property for the IComponentChangeService 
        /// </devdoc>
        private IComponentChangeService ChangeService {
            get {
                return
                    componentChangeSvc
                    ?? (componentChangeSvc = (IComponentChangeService)((IServiceProvider)this)
                                                                           .GetService(typeof(IComponentChangeService)));
            }
        }

        /// <devdoc> 
        /// Self-explanitory interface impl.
        /// </devdoc>
        IContainer ITypeDescriptorContext.Container {
            get {
                return designer.Component.Site != null
                           ? designer.Component.Site.Container
                           : null;
            }
        }

        /// <devdoc>
        /// Self-explanitory interface impl. 
        /// </devdoc>
        void ITypeDescriptorContext.OnComponentChanged() {
            ChangeService.OnComponentChanged(designer.Component, targetProperty, null, null);
        }


        /// <devdoc>
        /// Self-explanitory interface impl.
        /// </devdoc> 
        bool ITypeDescriptorContext.OnComponentChanging() {
            try {
                ChangeService.OnComponentChanging(designer.Component, targetProperty);
            } catch (CheckoutException checkoutException) {
                if (checkoutException == CheckoutException.Canceled) {
                    return false;
                }
                throw;
            }
            return true;
        }

        /// <devdoc>
        /// Self-explanitory interface impl. 
        /// </devdoc>
        object ITypeDescriptorContext.Instance {
            get { return designer.Component; }
        }

        /// <devdoc>
        /// Self-explanitory interface impl. 
        /// </devdoc>
        PropertyDescriptor ITypeDescriptorContext.PropertyDescriptor {
            get { return targetProperty; }
        }

        /// <devdoc>
        /// Self-explanitory interface impl. 
        /// </devdoc>
        object IServiceProvider.GetService(Type serviceType) {
            if (serviceType == typeof(ITypeDescriptorContext) ||
                serviceType == typeof(IWindowsFormsEditorService)) {
                return this;
            }

            if (designer.Component.Site != null) {
                return designer.Component.Site.GetService(serviceType);
            }
            return null;
        }

        /// <devdoc>
        /// Self-explanitory interface impl. 
        /// </devdoc> 
        void IWindowsFormsEditorService.CloseDropDown() {
            // we'll never be called to do this. 
            //
            Debug.Fail("NOTIMPL");
            return;
        }

        /// <devdoc> 
        /// Self-explanitory interface impl. 
        /// </devdoc>
        void IWindowsFormsEditorService.DropDownControl(Control control) {
            // nope, sorry
            //
            Debug.Fail("NOTIMPL");
            return;
        }

        /// <devdoc> 
        /// Self-explanitory interface impl.
        /// </devdoc> 
        DialogResult IWindowsFormsEditorService.ShowDialog(Form dialog) {
            var uiSvc = (IUIService)((IServiceProvider)this).GetService(typeof(IUIService));
            if (uiSvc != null) {
                return uiSvc.ShowDialog(dialog);
            } else {
                return dialog.ShowDialog(designer.Component as IWin32Window);
            }
        }

        /// <devdoc>
        /// When the verb is invoked, use all the stuff above to show the dialog, etc.
        /// </devdoc>
        private void OnEditItems(object sender, EventArgs e) {
            var propertyValue = targetProperty.GetValue(designer.Component);
            if (propertyValue == null) {
                return;
            }
            var itemsEditor = TypeDescriptor.GetEditor(propertyValue, typeof(UITypeEditor)) as CollectionEditor;

            Debug.Assert(itemsEditor != null, "Didn't get a collection editor for type '" + targetProperty.PropertyType.FullName + "'");
            if (itemsEditor != null) {
                itemsEditor.EditValue(this, this, propertyValue);
            }
        }
    }
}